<template>
    <el-dialog v-model="visible" fullscreen :show-close="false">
        <template #header>
            <span>
                正在与{{ webSocketStore.videoInfo.realname
                }}{{ webSocketStore.callType === 'video' ? '视频' : '语音' }}通话
            </span>
        </template>
        <div class="callContent">
            <div v-if="webSocketStore.callType === 'video'" class="videoContainer">
                <video ref="local" class="local-video" autoplay playsinline muted></video>
                <video ref="remote" class="remote-video" autoplay playsinline></video>
            </div>
            <div v-else-if="webSocketStore.callType === 'audio'" class="audioContainer">
                <div class="incoming-message">
                    <img :src="webSocketStore.videoInfo.avatar" class="showImg" />
                    <span class="realname">
                        {{ webSocketStore.videoInfo.realname }}
                    </span>
                </div>
                <audio ref="remote" autoplay playsinline></audio>
                <audio ref="local" autoplay playsinline muted></audio>
            </div>
            <div class="hangUp">
                <HangUp @click="endVideo"></HangUp>
            </div>
        </div>
    </el-dialog>
    <el-dialog
        v-model="incomingCall"
        width="400"
        align-center
        :close-on-click-modal="false"
        :show-close="false"
    >
        <template #header>
            <span>
                {{ webSocketStore.videoInfo.realname }}邀请您进行{{
                    webSocketStore.callType === 'video' ? '视频' : '语音'
                }}通话
            </span>
        </template>
        <div class="incoming-message">
            <img :src="webSocketStore.videoInfo.avatar" class="incoming-img" />
            <span class="incoming-name">
                {{ webSocketStore.videoInfo.realname }}
            </span>
        </div>
        <template #footer>
            <div class="dialog-footer">
                <el-button type="primary" @click="answerCall(webSocketStore.callType)">
                    接听
                </el-button>
                <el-button type="danger" @click="hangUp">挂断</el-button>
            </div>
        </template>
    </el-dialog>
</template>

<script setup lang="ts">
import { ref, nextTick, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import useMessagesStore from '@/store/modules/messages'
import useWebSocketStore from '@/store/modules/wschat'
import {
    SET_CALLTO,
    SET_CALLTYPE,
    SET_ISCALLING,
    GET_INCOMINGCALL,
    SET_VIDEOINFO,
    GET_VIDEOINFO,
} from '@/utils/keepCall'

let messageStore = useMessagesStore()
let local = ref<HTMLVideoElement | HTMLAudioElement>()
let remote = ref<HTMLVideoElement | HTMLAudioElement>()
let localStream: MediaStream | null = null
let visible = ref<boolean>(false) //正在通话的通话框
let incomingCall = ref<boolean>(false)
let userStore = useUserStore()
let webSocketStore = useWebSocketStore()

//尝试调取本地摄像头或音频的方法
async function retrieveCamera(callType: string) {
    try {
        if (callType === 'video') {
            localStream = await navigator.mediaDevices.getUserMedia({
                video: { width: 1920, height: 1080 },
                audio: true,
            })
        } else if (callType === 'audio') {
            localStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
            })
        }
        local.value!.srcObject = localStream
    } catch (error) {
        return ElMessage.error('本地设备调取失败')
    }
}

//语音视频通话刷新重连的方法
const reconnect = () => {
    let message = {
        message_type: 'reconnect',
        receiver_id: GET_VIDEOINFO().userId,
    }
    webSocketStore.sendMessage(message)
}

//调用WebRTC进行聊天的方法
async function RTCChat(sender_id: number, receiver_id: number) {
    //初始化peer连接
    webSocketStore.peer = new RTCPeerConnection()
    webSocketStore.peer.onicecandidate = (e: RTCPeerConnectionIceEvent) => {
        if (e.candidate) {
            let message = {
                message_type: 'ice',
                sender_id: sender_id,
                receiver_id: receiver_id,
                iceCandidate: e.candidate,
            }
            webSocketStore.sendMessage(message)
        }
    }
    webSocketStore.peer.ontrack = (e: RTCTrackEvent) => {
        if (e.streams && e.streams[0]) {
            remote.value!.srcObject = e.streams[0]
        }
    }
    //将本地视频流或音频流添加到对等轨道中
    localStream?.getTracks().forEach((track) => webSocketStore.peer!.addTrack(track, localStream!))
}

//发起方开始请求通话
async function beginVideo(callType: string) {
    //发起通话状态改变
    webSocketStore.callTo = true
    webSocketStore.callType = callType
    SET_CALLTO(true)
    SET_CALLTYPE(callType)
    //打开通话窗口
    visible.value = true
    //将通话发起者的部分信息通过网络发送给接听者
    let message = {
        message_type: 'video_info',
        userId: userStore.userId,
        avatar: userStore.avatar,
        realname: userStore.realname,
        receiver_id: messageStore.chattingFriend.userId,
        showMessage: '您有一条新的来电',
        callType: callType,
    }
    webSocketStore.sendMessage(message)
    //将正在通话的好友的部分信息储存到仓库中
    webSocketStore.videoInfo = messageStore.chattingFriend
    SET_VIDEOINFO(messageStore.chattingFriend)
    //等待DOM更新
    await nextTick()
    //尝试调取本地摄像头或音频
    await retrieveCamera(callType)
    //启动WebRTC连接
    await RTCChat(userStore.userId, messageStore.chattingFriend.userId)
    await webSocketStore.sendSDP('offer', userStore.userId, messageStore.chattingFriend.userId)
}

//结束通话
async function endVideo() {
    //关闭通话窗口
    visible.value = false
    await nextTick()
    //将结束通话的信号发给对方
    let message = {
        message_type: 'video_end',
        receiver_id: webSocketStore.videoInfo.userId,
        showMessage: '对方已挂断',
    }
    webSocketStore.sendMessage(message)
    // 结束通话
    endCall()
    //将仓库状态回归初始状态
    webSocketStore.initializeWebsocketStore()
}

// 拒绝通话
async function hangUp() {
    //关闭来电提示窗口
    incomingCall.value = false
    await nextTick()
    //将拒绝通话的信号发给对方
    let message = {
        message_type: 'video_refuse',
        receiver_id: webSocketStore.videoInfo.userId,
        showMessage: '对方已拒绝',
    }
    webSocketStore.sendMessage(message)
    // 结束通话
    endCall()
    //将仓库状态回归初始状态
    webSocketStore.initializeWebsocketStore()
}

//接听通话
async function answerCall(callType: string) {
    //将仓库的通话状态设置为有
    webSocketStore.iscalling = true
    SET_ISCALLING(true)
    //将接受通话的信号发给对方
    let message = {
        message_type: 'video_receive',
        receiver_id: webSocketStore.receivedSDP.sender_id,
        showMessage: '已接通',
    }
    webSocketStore.sendMessage(message)
    //关闭来电通知框
    incomingCall.value = false
    //开启通话框
    visible.value = true
    await nextTick()
    //尝试调取本地摄像头或音频
    await retrieveCamera(callType)
    //启动WebRTC连接
    await RTCChat(userStore.userId, webSocketStore.receivedSDP.sender_id)
    await webSocketStore.sendSDP('answer', userStore.userId, webSocketStore.receivedSDP.sender_id)
}

//结束通话
function endCall() {
    // 关闭WebRTC连接
    if (webSocketStore.peer) {
        webSocketStore.peer.ontrack = null // 移除事件处理程序
        webSocketStore.peer.onicecandidate = null // 移除事件处理程序
        webSocketStore.peer.close() // 关闭连接
        webSocketStore.peer = null
    }

    // 停止本地流
    if (localStream) {
        localStream.getTracks().forEach((track) => track.stop()) // 停止所有轨道
        localStream = null // 清理本地流
    }

    // 停止远程流
    if (remote.value && remote.value.srcObject) {
        const remoteStream = remote.value.srcObject as MediaStream
        remoteStream.getTracks().forEach((track) => track.stop()) // 停止所有轨道
        remote.value.srcObject = null // 清理远程流
    }

    // 清理本地元素
    if (local.value) {
        local.value.srcObject = null // 清理本地流
    }
}

//监听是否点击通话，开始通话业务
watch(
    () => webSocketStore.callTo,
    (newVal) => {
        if (newVal) {
            beginVideo(webSocketStore.callType)
        } else if (!newVal) {
            // 结束通话
            endCall()
            incomingCall.value = false
            visible.value = false
        }
    },
)

//监听是否有通话请求，显示来电通知
watch(
    () => webSocketStore.incomingCall,
    (newVal) => {
        if (newVal) {
            incomingCall.value = true
        } else {
            // 结束通话
            endCall()
            incomingCall.value = false
        }
    },
)

//监听是否仍在打电话给对方，如果未接通对方挂断或者在接通状态下，其中一方挂断，则清理本地资源
watch(
    () => webSocketStore.iscalling,
    (newVal) => {
        if (!newVal) {
            // 结束通话
            endCall()
            visible.value = false
        }
    },
)

//监听信息变化，发送给用户通话提示信息
watch(
    () => webSocketStore.showMessage,
    (newVal) => {
        if (newVal) {
            ElMessage(webSocketStore.showMessage)
        }
    },
)

watch(
    () => webSocketStore.isRequestReconnect,
    async (newVal) => {
        //正在通话时收到请求重连关闭WebRTC连接
        if (newVal) {
            // 关闭WebRTC连接
            if (webSocketStore.peer) {
                webSocketStore.peer.ontrack = null // 移除事件处理程序
                webSocketStore.peer.onicecandidate = null // 移除事件处理程序
                webSocketStore.peer.close() // 关闭连接
                webSocketStore.peer = null
            }

            // 停止远程流
            if (remote.value && remote.value.srcObject) {
                const remoteStream = remote.value.srcObject as MediaStream
                remoteStream.getTracks().forEach((track) => track.stop()) // 停止所有轨道
                remote.value.srcObject = null // 清理远程流
            }
        }
        //正在通话时接收者刷新，收到请求重连消息后发送者重新发起通话请求
        if (newVal && webSocketStore.callTo) {
            RTCChat(userStore.userId, webSocketStore.receivedSDP.sender_id)
            await webSocketStore.sendSDP(
                'offer',
                userStore.userId,
                webSocketStore.receivedSDP.sender_id,
            )
            webSocketStore.isRequestReconnect = false
            let message = {
                message_type: 'callReconnectSuccess',
                receiver_id: GET_VIDEOINFO().userId,
            }
            webSocketStore.sendMessage(message)
        }
    },
)

//通话发起者正在通话时刷新，需要等待WebSocket连接成功后才能重新发起通话请求
watch(
    () => webSocketStore.isConnected,
    async (newVal) => {
        if (newVal && webSocketStore.iscalling) {
            //触发重连
            reconnect()
            if (webSocketStore.callTo) {
                try {
                    RTCChat(userStore.userId, webSocketStore.receivedSDP.sender_id)
                    await webSocketStore.sendSDP(
                        'offer',
                        userStore.userId,
                        webSocketStore.receivedSDP.sender_id,
                    )
                } catch (error) {
                    ElMessage.error('断线重连失败')
                }
                let message = {
                    message_type: 'callReconnectSuccess',
                    receiver_id: GET_VIDEOINFO().userId,
                }
                webSocketStore.sendMessage(message)
            }
        }
    },
)

//通话接收者刷新或断网后重新收到请求进行自动应答
watch(
    () => webSocketStore.callReconnected,
    async (newVal) => {
        if (newVal) {
            try {
                RTCChat(userStore.userId, webSocketStore.receivedSDP.sender_id)
                await webSocketStore.sendSDP(
                    'answer',
                    userStore.userId,
                    webSocketStore.receivedSDP.sender_id,
                )
            } catch (error) {
                ElMessage.error('断线重连失败')
            }
            webSocketStore.callReconnected = false
        }
    },
)

onMounted(async () => {
    //通话接收者在收到请求时还未接听的情况下自己刷新，这里只重新加载来电聊天框
    if (!webSocketStore.iscalling && webSocketStore.incomingCall) {
        incomingCall.value = GET_INCOMINGCALL()
        return
    }
    //通话发起者在请求时对方还未接听的情况下自己刷新，重新进行请求
    if (!webSocketStore.iscalling && webSocketStore.callTo) {
        beginVideo(webSocketStore.callType)
        return
    }
    //正在通话中刷新，显示通话页面并重新请求摄像头等设备
    if (webSocketStore.iscalling) {
        visible.value = true
        await retrieveCamera(webSocketStore.callType)
        return
    }
})
</script>

<style scoped lang="scss">
.callContent {
    min-height: 91vh;
    background: url('@/assets/images/background.jpg') no-repeat;
    background-size: cover;
    background-position: center;
    @include flex-layout(flex, normal, normal, nowrap, column);

    .videoContainer {
        width: 100%;
        height: 75vh;
        @include flex-layout();

        .local-video {
            width: 50%;
            height: 100%;
            object-fit: cover;
            background-color: #ebeef5;
        }

        .remote-video {
            width: 50%;
            height: 100%;
            object-fit: cover;
            background-color: #f2f6fc;
        }
    }

    .audioContainer {
        width: 100%;
        height: 75vh;
        @include flex-layout(flex, center, center);

        .showImg {
            width: 8rem;
            height: 8rem;
            border-radius: 1rem;
        }

        .realname {
            color: white;
            height: 2.4rem;
            line-height: 2rem;
            font-size: 2rem;
            margin-top: 1.2rem;
        }
    }

    .hangUp {
        flex: 1;
        @include flex-layout(flex, center, center);
    }
}

.incoming-message {
    @include flex-layout(flex, center, center, nowrap, column);

    .incoming-img {
        width: calc($base-menu-height - 1rem);
        height: calc($base-menu-height - 1rem);
        border-radius: 0.5rem;
    }

    .incoming-name {
        height: 1.2rem;
        line-height: 1rem;
        font-size: 1rem;
        margin-top: 0.6rem;
    }
}
</style>
